-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add drag-and-drop agent reordering and auto-update on version mismatch #206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Replace hardcoded Chinese strings with proper i18n translation keys in the tier selector component. This fixes console warnings about missing translation keys when using the zh locale. - Add speedLabels, reasoningLabels, and features keys to app.json - Add multiplier key to tierSelector in all locales - Add recommended key to common.json in all locales - Refactor TierInfoModal.tsx to use translation key references Co-Authored-By: Claude <[email protected]>
Integrate dnd-kit into existing AgentList and AgentListItem components to support drag-and-drop reordering in both the sidebar and spatial focused view. Backend: - Add sort_order field to Agent model - Add PATCH endpoint for bulk reordering agents - Add migration for sort_order column Frontend: - Add sortable prop to AgentList with DndContext/SortableContext - Add dragHandleProps to AgentListItem for drag behavior - Use plain div (not motion.div) when sortable to avoid animation conflicts - Use set-based comparison for state sync (only reset on add/remove) - Add reorderAgents action to agentSlice
Add automatic update mechanism that detects when the frontend version doesn't match the backend version and refreshes the page to fetch the latest assets. This ensures users with cached frontends always get updated code without manually clearing their browser cache. - Add useAutoUpdate hook with retry logic (max 3 attempts) - Add UpdateOverlay component for update feedback - Clear service workers and caches before reload - Store retry state in localStorage to prevent infinite loops
Reviewer's Guide在前端 UI 中为 Agent 实现拖拽排序,并在后端通过持久化的 Agent 拖拽排序并在后端持久化的时序图sequenceDiagram
actor User
participant AgentList
participant FocusedView
participant XyzenAgent
participant useXyzen
participant AgentSlice
participant BackendAPI_reorder
participant AgentRepository
participant Database
User->>AgentList: drag agent
AgentList->>AgentList: update local items order
AgentList-->>FocusedView: onReorder(agentIds)
FocusedView->>useXyzen: reorderAgents(agentIds)
useXyzen->>AgentSlice: reorderAgents(agentIds)
AgentSlice->>AgentSlice: optimistic reorder in state
AgentSlice->>BackendAPI_reorder: PUT /xyzen/api/v1/agents/reorder
BackendAPI_reorder->>AgentRepository: update_agents_sort_order(user_id, agent_ids)
AgentRepository->>Database: update Agent.sort_order
Database-->>AgentRepository: ok
AgentRepository-->>BackendAPI_reorder: flush
BackendAPI_reorder-->>AgentSlice: 204 No Content
AgentSlice-->>useXyzen: resolve
useXyzen-->>FocusedView: resolve
FocusedView-->>AgentList: no-op (UI already updated)
基于后端版本的前端自动更新时序图sequenceDiagram
participant Xyzen
participant AutoUpdateWrapper
participant useAutoUpdate
participant useBackendVersion
participant systemService
participant BackendAPI_version
participant clearCachesAndServiceWorkers
participant Window
Xyzen->>AutoUpdateWrapper: render with mainLayout
AutoUpdateWrapper->>useAutoUpdate: enabled=true
useAutoUpdate->>useBackendVersion: query backend version
useBackendVersion->>systemService: getVersion()
systemService->>BackendAPI_version: GET /system/version
BackendAPI_version-->>systemService: {version}
systemService-->>useBackendVersion: backendData
useBackendVersion-->>useAutoUpdate: data, isLoading=false
useAutoUpdate->>useAutoUpdate: compare getFrontendVersion() vs backendData.version
alt versions_match
useAutoUpdate->>useAutoUpdate: clearUpdateState()
useAutoUpdate-->>AutoUpdateWrapper: {isUpdating=false,targetVersion=null}
AutoUpdateWrapper-->>Xyzen: render children
else versions_mismatch
useAutoUpdate->>useAutoUpdate: getUpdateState() and increment retryCount
useAutoUpdate->>useAutoUpdate: saveUpdateState({targetVersion,retryCount})
useAutoUpdate->>clearCachesAndServiceWorkers: performUpdate(version)
clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: unregister service workers
clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: clear caches
clearCachesAndServiceWorkers-->>useAutoUpdate: done
useAutoUpdate->>Window: reload()
end
Agent sort_order 与用户关系的 ER 图erDiagram
USER ||--o{ AGENT : owns
USER {
string id PK
}
AGENT {
uuid id PK
string user_id FK
string name
int sort_order
datetime created_at
datetime updated_at
}
更新后的 Agent 排序与自动更新逻辑类图classDiagram
class Agent {
+UUID id
+string name
+string user_id
+string created_at
+string updated_at
+int sort_order
+string prompt
}
class AgentRepository {
+get_agents_by_user(user_id: string) Sequence~Agent~
+create_agent(agent_data: AgentCreate, user_id: string) Agent
+update_agents_sort_order(user_id: string, agent_ids: list~UUID~) None
}
class AgentSlice {
+Agent[] agents
+reorderAgents(agentIds: string[]) Promise~void~
+fetchAgents() Promise~void~
}
class AgentList {
+AgentListProps props
+Agent[] items
+string activeId
+sortable: boolean
+onReorder(agentIds: string[]) void
+handleDragStart(event: DragStartEvent) void
+handleDragEnd(event: DragEndEvent) void
+handleDragCancel() void
+renderOverlayItem() ReactNode
}
class SortableItem {
+agent: Agent
+variant: string
+isMarketplacePublished: boolean
+lastConversationTime: string
+isSelected: boolean
+status: string
+role: string
}
class DragHandleProps {
+attributes: React.HTMLAttributes~HTMLElement~
+listeners: React.DOMAttributes~HTMLElement~
}
class AgentListItem {
+agent: Agent
+variant: string
+isDragging: boolean
+dragHandleProps: DragHandleProps
+style: React.CSSProperties
+setNodeRef(node: HTMLElement) void
}
class XyzenAgent {
+agents: Agent[]
+reorderAgents(agentIds: string[]) Promise~void~
+handleReorder(agentIds: string[]) Promise~void~
}
class FocusedView {
+agentsForList: Agent[]
+reorderAgents(nodeIds: string[]) Promise~void~
+handleReorder(nodeIds: string[]) Promise~void~
}
class AutoUpdateWrapper {
+children: ReactNode
}
class useAutoUpdate {
+isUpdating: boolean
+targetVersion: string
+performUpdate(version: string) Promise~void~
}
class UpdateOverlay {
+targetVersion: string
}
AgentSlice --> Agent : manages
AgentRepository --> Agent : persists
AgentList --> Agent : displays
AgentList --> SortableItem : wraps_items
SortableItem --> AgentListItem : renders
AgentListItem --> DragHandleProps : uses
XyzenAgent --> AgentList : renders_detailed
FocusedView --> AgentList : renders_compact
XyzenAgent --> AgentSlice : calls_reorderAgents
FocusedView --> AgentSlice : calls_reorderAgents
AutoUpdateWrapper --> useAutoUpdate : uses
AutoUpdateWrapper --> UpdateOverlay : renders_when_updating
文件级改动
Tips and commands与 Sourcery 交互
自定义你的体验访问你的 dashboard 以:
获取帮助Original review guide in EnglishReviewer's GuideImplements drag-and-drop reordering for agents in the UI backed by persistent sort_order in the backend, plus introduces an automatic frontend update mechanism based on backend version and improves tier info i18n structure. Sequence diagram for agent drag-and-drop reordering with backend persistencesequenceDiagram
actor User
participant AgentList
participant FocusedView
participant XyzenAgent
participant useXyzen
participant AgentSlice
participant BackendAPI_reorder
participant AgentRepository
participant Database
User->>AgentList: drag agent
AgentList->>AgentList: update local items order
AgentList-->>FocusedView: onReorder(agentIds)
FocusedView->>useXyzen: reorderAgents(agentIds)
useXyzen->>AgentSlice: reorderAgents(agentIds)
AgentSlice->>AgentSlice: optimistic reorder in state
AgentSlice->>BackendAPI_reorder: PUT /xyzen/api/v1/agents/reorder
BackendAPI_reorder->>AgentRepository: update_agents_sort_order(user_id, agent_ids)
AgentRepository->>Database: update Agent.sort_order
Database-->>AgentRepository: ok
AgentRepository-->>BackendAPI_reorder: flush
BackendAPI_reorder-->>AgentSlice: 204 No Content
AgentSlice-->>useXyzen: resolve
useXyzen-->>FocusedView: resolve
FocusedView-->>AgentList: no-op (UI already updated)
Sequence diagram for automatic frontend update based on backend versionsequenceDiagram
participant Xyzen
participant AutoUpdateWrapper
participant useAutoUpdate
participant useBackendVersion
participant systemService
participant BackendAPI_version
participant clearCachesAndServiceWorkers
participant Window
Xyzen->>AutoUpdateWrapper: render with mainLayout
AutoUpdateWrapper->>useAutoUpdate: enabled=true
useAutoUpdate->>useBackendVersion: query backend version
useBackendVersion->>systemService: getVersion()
systemService->>BackendAPI_version: GET /system/version
BackendAPI_version-->>systemService: {version}
systemService-->>useBackendVersion: backendData
useBackendVersion-->>useAutoUpdate: data, isLoading=false
useAutoUpdate->>useAutoUpdate: compare getFrontendVersion() vs backendData.version
alt versions_match
useAutoUpdate->>useAutoUpdate: clearUpdateState()
useAutoUpdate-->>AutoUpdateWrapper: {isUpdating=false,targetVersion=null}
AutoUpdateWrapper-->>Xyzen: render children
else versions_mismatch
useAutoUpdate->>useAutoUpdate: getUpdateState() and increment retryCount
useAutoUpdate->>useAutoUpdate: saveUpdateState({targetVersion,retryCount})
useAutoUpdate->>clearCachesAndServiceWorkers: performUpdate(version)
clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: unregister service workers
clearCachesAndServiceWorkers->>clearCachesAndServiceWorkers: clear caches
clearCachesAndServiceWorkers-->>useAutoUpdate: done
useAutoUpdate->>Window: reload()
end
ER diagram for Agent sort_order and user relationshiperDiagram
USER ||--o{ AGENT : owns
USER {
string id PK
}
AGENT {
uuid id PK
string user_id FK
string name
int sort_order
datetime created_at
datetime updated_at
}
Class diagram for updated agent ordering and auto-update logicclassDiagram
class Agent {
+UUID id
+string name
+string user_id
+string created_at
+string updated_at
+int sort_order
+string prompt
}
class AgentRepository {
+get_agents_by_user(user_id: string) Sequence~Agent~
+create_agent(agent_data: AgentCreate, user_id: string) Agent
+update_agents_sort_order(user_id: string, agent_ids: list~UUID~) None
}
class AgentSlice {
+Agent[] agents
+reorderAgents(agentIds: string[]) Promise~void~
+fetchAgents() Promise~void~
}
class AgentList {
+AgentListProps props
+Agent[] items
+string activeId
+sortable: boolean
+onReorder(agentIds: string[]) void
+handleDragStart(event: DragStartEvent) void
+handleDragEnd(event: DragEndEvent) void
+handleDragCancel() void
+renderOverlayItem() ReactNode
}
class SortableItem {
+agent: Agent
+variant: string
+isMarketplacePublished: boolean
+lastConversationTime: string
+isSelected: boolean
+status: string
+role: string
}
class DragHandleProps {
+attributes: React.HTMLAttributes~HTMLElement~
+listeners: React.DOMAttributes~HTMLElement~
}
class AgentListItem {
+agent: Agent
+variant: string
+isDragging: boolean
+dragHandleProps: DragHandleProps
+style: React.CSSProperties
+setNodeRef(node: HTMLElement) void
}
class XyzenAgent {
+agents: Agent[]
+reorderAgents(agentIds: string[]) Promise~void~
+handleReorder(agentIds: string[]) Promise~void~
}
class FocusedView {
+agentsForList: Agent[]
+reorderAgents(nodeIds: string[]) Promise~void~
+handleReorder(nodeIds: string[]) Promise~void~
}
class AutoUpdateWrapper {
+children: ReactNode
}
class useAutoUpdate {
+isUpdating: boolean
+targetVersion: string
+performUpdate(version: string) Promise~void~
}
class UpdateOverlay {
+targetVersion: string
}
AgentSlice --> Agent : manages
AgentRepository --> Agent : persists
AgentList --> Agent : displays
AgentList --> SortableItem : wraps_items
SortableItem --> AgentListItem : renders
AgentListItem --> DragHandleProps : uses
XyzenAgent --> AgentList : renders_detailed
FocusedView --> AgentList : renders_compact
XyzenAgent --> AgentSlice : calls_reorderAgents
FocusedView --> AgentSlice : calls_reorderAgents
AutoUpdateWrapper --> useAutoUpdate : uses
AutoUpdateWrapper --> UpdateOverlay : renders_when_updating
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:
- 在 AgentList 中,当 ID 集合不同时,
setItems(agents)会在渲染过程中被直接调用,这是一个反模式,可能导致渲染循环;请将这段同步逻辑移到一个监听agents的useEffect中。 - AgentList 的本地排序状态只会在 agent ID 集合发生变化时同步,因此当后端驱动的重排(ID 相同但顺序不同)发生时,前端永远不会反映出来;建议在传入的
agents顺序发生变化时也进行同步,而不仅仅是在成员变化时。 - CompactAgentListItem 从
<button>改成了<div>,但没有添加键盘/aria 处理,这破坏了按钮语义和可访问性;请要么继续使用 button,要么添加合适的role="button"、tabIndex和键盘事件处理。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In AgentList, `setItems(agents)` is called directly during render when the ID sets differ, which is an anti-pattern and can cause render loops; move this synchronization logic into a `useEffect` that watches `agents` instead.
- The AgentList local ordering state only syncs when the set of agent IDs changes, so backend-driven reorders (same IDs, different order) will never be reflected; consider also syncing when the incoming `agents` order changes, not just their membership.
- CompactAgentListItem was changed from a `<button>` to a `<div>` without adding keyboard/aria handling, which breaks button semantics and accessibility; either keep a button or add appropriate `role="button"`, `tabIndex`, and keyboard event handling.
## Individual Comments
### Comment 1
<location> `web/src/hooks/useAutoUpdate.ts:53-54` </location>
<code_context>
+/**
+ * Saves update state to localStorage
+ */
+function saveUpdateState(state: UpdateState): void {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+}
+
</code_context>
<issue_to_address>
**suggestion:** Guard `localStorage.setItem` to avoid runtime errors in restricted environments.
`getUpdateState` already guards `localStorage` access with try/catch, but `saveUpdateState` (and `clearUpdateState`) do not. In environments with disabled storage or quota exceeded, `setItem`/`removeItem` can throw and break the auto-update flow.
Please wrap these calls in try/catch (mirroring `getUpdateState`) and treat persistence as best-effort so storage errors don’t crash auto-update.
</issue_to_address>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- In AgentList,
setItems(agents)is called directly during render when the ID sets differ, which is an anti-pattern and can cause render loops; move this synchronization logic into auseEffectthat watchesagentsinstead. - The AgentList local ordering state only syncs when the set of agent IDs changes, so backend-driven reorders (same IDs, different order) will never be reflected; consider also syncing when the incoming
agentsorder changes, not just their membership. - CompactAgentListItem was changed from a
<button>to a<div>without adding keyboard/aria handling, which breaks button semantics and accessibility; either keep a button or add appropriaterole="button",tabIndex, and keyboard event handling.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In AgentList, `setItems(agents)` is called directly during render when the ID sets differ, which is an anti-pattern and can cause render loops; move this synchronization logic into a `useEffect` that watches `agents` instead.
- The AgentList local ordering state only syncs when the set of agent IDs changes, so backend-driven reorders (same IDs, different order) will never be reflected; consider also syncing when the incoming `agents` order changes, not just their membership.
- CompactAgentListItem was changed from a `<button>` to a `<div>` without adding keyboard/aria handling, which breaks button semantics and accessibility; either keep a button or add appropriate `role="button"`, `tabIndex`, and keyboard event handling.
## Individual Comments
### Comment 1
<location> `web/src/hooks/useAutoUpdate.ts:53-54` </location>
<code_context>
+/**
+ * Saves update state to localStorage
+ */
+function saveUpdateState(state: UpdateState): void {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+}
+
</code_context>
<issue_to_address>
**suggestion:** Guard `localStorage.setItem` to avoid runtime errors in restricted environments.
`getUpdateState` already guards `localStorage` access with try/catch, but `saveUpdateState` (and `clearUpdateState`) do not. In environments with disabled storage or quota exceeded, `setItem`/`removeItem` can throw and break the auto-update flow.
Please wrap these calls in try/catch (mirroring `getUpdateState`) and treat persistence as best-effort so storage errors don’t crash auto-update.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- Move AgentList state sync from render to useEffect to prevent render loops
- Add isDraggingRef to prevent backend sync during active drag operations
- Restore keyboard accessibility to CompactAgentListItem (role, tabIndex,
aria-pressed, onKeyDown handler)
- Guard localStorage writes with try/catch in useAutoUpdate to handle
restricted environments
* feat: add drag-and-drop agent reordering and auto-update on version mismatch (#206) * fix: resolve i18next missing key warnings in TierInfoModal Replace hardcoded Chinese strings with proper i18n translation keys in the tier selector component. This fixes console warnings about missing translation keys when using the zh locale. - Add speedLabels, reasoningLabels, and features keys to app.json - Add multiplier key to tierSelector in all locales - Add recommended key to common.json in all locales - Refactor TierInfoModal.tsx to use translation key references Co-Authored-By: Claude <[email protected]> * feat: add drag-and-drop agent reordering Integrate dnd-kit into existing AgentList and AgentListItem components to support drag-and-drop reordering in both the sidebar and spatial focused view. Backend: - Add sort_order field to Agent model - Add PATCH endpoint for bulk reordering agents - Add migration for sort_order column Frontend: - Add sortable prop to AgentList with DndContext/SortableContext - Add dragHandleProps to AgentListItem for drag behavior - Use plain div (not motion.div) when sortable to avoid animation conflicts - Use set-based comparison for state sync (only reset on add/remove) - Add reorderAgents action to agentSlice * feat: auto-refresh frontend on version mismatch with backend Add automatic update mechanism that detects when the frontend version doesn't match the backend version and refreshes the page to fetch the latest assets. This ensures users with cached frontends always get updated code without manually clearing their browser cache. - Add useAutoUpdate hook with retry logic (max 3 attempts) - Add UpdateOverlay component for update feedback - Clear service workers and caches before reload - Store retry state in localStorage to prevent infinite loops * fix: address PR review feedback for drag-and-drop agent reordering - Move AgentList state sync from render to useEffect to prevent render loops - Add isDraggingRef to prevent backend sync during active drag operations - Restore keyboard accessibility to CompactAgentListItem (role, tabIndex, aria-pressed, onKeyDown handler) - Guard localStorage writes with try/catch in useAutoUpdate to handle restricted environments --------- Co-authored-by: Claude <[email protected]> * fix: resolve version mismatch causing refresh loops in test environment (#207) * fix: move the comment to correct position * fix: fix the test environment version * fix: remove auto-update feature to prevent refresh loops The auto-update mechanism causes infinite refresh loops when frontend and backend versions mismatch in test environment. Remove the feature entirely until a more robust solution is implemented. - Delete useAutoUpdate hook and UpdateOverlay component - Remove AutoUpdateWrapper from App.tsx * Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh (#208) * feat: add simple landing page and logout button * fix(web): unify tool call rendering in agent timeline after refresh; add landing page - Render tool calls as pills with a shared details modal (args/results/errors) - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages - Remove legacy ToolCallCard-based rendering path * fix(web): address review feedback for tool call modal + typewriter - Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja - Memoize tool result parsing and image URL derivation in ToolCallDetails - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation - Remove redundant typewriter cursor className conditional and fix unused state var * feat: message editing and deletion (#209) * feat: add message editing and deletion with truncate-regenerate flow - Add PATCH /messages/{id} endpoint for editing user messages - Add DELETE /messages/{id} endpoint for deleting any message - Add regenerate WebSocket handler for re-running agent after edit - Add edit/delete UI to ChatBubble with hover actions - Add i18n translations for en/zh/ja Includes fixes from code review: - Fix pre-deduction error handling to skip dispatch on any failure - Reset responding state before regeneration to prevent stuck UI - Add message ownership verification before edit/delete operations * fix: improve the code according to sourcery review * fix: use version+SHA for beta image tags to ensure unique deployments (#211) The previous approach used only the pyproject.toml version (e.g., 1.0.16) which caused Kubernetes to not pull new images when multiple commits used the same version tag. Now uses format: {version}-{short-sha} (e.g., 1.0.16-f13e3c0) Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * feat: conversation interrupt/abort functionality (#212) * feat: add conversation interrupt/abort functionality - Add stop button and Escape key shortcut to abort streaming generation - Implement Redis-based signaling between API server and Celery worker - Worker checks abort signal every 0.5s and gracefully stops streaming - Save partial content and perform partial billing on abort - Add visual indicator for cancelled/aborted messages - Add timeout fallback (10s) to reset UI if backend doesn't respond - Add i18n strings for stop/stopping/escToStop in en/zh/ja * fix: address abort feature edge cases from code review - Clear stale abort signals at task start to prevent race condition when user reconnects quickly after disconnect - Finalize AgentRun with 'failed' status on unhandled exceptions to ensure consistent DB state across all exit paths - Move time import to module level (was inline import) * fix: address Sourcery review feedback for abort feature - Reuse existing Redis client for abort checks instead of creating new connections on each tick (performance improvement) - Fix potential Redis connection leaks in ABORT and disconnect handlers by using try/finally pattern - Track and cleanup abort timeout in frontend to prevent stale timers from racing with subsequent abort requests * Preserve phase text when copying streamed output (#210) * Preserve phase text when copying streamed output * Extract agent phase content helper * fix: use existing PhaseExecution type and correct useMemo dependencies - Replace inline PhaseWithStreamedContent type with Pick<PhaseExecution, 'streamedContent'> for type consistency across the codebase - Fix useMemo dependency array to use agentExecution?.phases instead of agentExecution to ensure proper recalculation when phases array changes * Fix streaming agent messages and prevent deleting non-persisted messages (#213) * Fix message deletion for agent executions * Address Sourcery review: stricter UUID validation and safer id assignment - Add isValidUuid utility with canonical UUID pattern (8-4-4-4-12 format) to replace loose regex that accepted invalid strings like all-hyphens - Fix streaming_start handler to only set id when eventData.id is truthy, preventing accidental overwrites with undefined/null - Improve delete guard with contextual messages ("still streaming" vs "not saved yet") and change notification type to warning - Add comprehensive tests for isValidUuid covering valid UUIDs, client IDs, invalid formats, and edge cases Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * fix: emit message_saved event after stream abort (#215) * fix: emit message_saved event after stream abort When a user interrupts a streaming response, the message is saved to the database but the frontend never receives the message_saved event. This leaves the message with a temporary stream_ prefix ID, preventing deletion until page refresh. Now the MESSAGE_SAVED event is emitted after db.commit() in the abort handler, before STREAM_ABORTED, so the frontend updates the message ID to the real UUID and deletion works immediately. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * fix: always show latest topic when clicking an agent Unify sidebar and spatial workspace to use the same logic for selecting topics. Both now fetch from backend and always show the most recently updated topic (by updated_at) instead of remembering previously active topics. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * feat: improve message editing UX with edit-only option and assistant editing - Add "Edit" and "Edit & Regenerate" dropdown options for user messages - Allow editing assistant messages (content-only, no regeneration) - Add copy button to user messages - Move assistant message actions to top-right for better UX - Add auto-resizing textarea for editing long messages - Update backend to support truncate_and_regenerate flag Co-Authored-By: Claude Opus 4.5 <[email protected]> * refactor: extract message content resolution into dedicated module Extract scattered content resolution logic into core/chat/messageContent.ts with two main utilities: - resolveMessageContent(): Single source of truth for content priority - getMessageDisplayMode(): Explicit rendering mode determination This refactoring: - Reduces ChatBubble.tsx complexity (60+ line IIFE → 30 line switch) - Fixes inconsistency between copy/edit and display logic - Makes content source priority explicit and documented - Adds guard for empty content to avoid rendering empty divs - Improves maintainability with testable pure functions Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * fix: deep research structured output and recursion limit (#216) - Use function_calling method for structured output in clarify node. The default json_mode doesn't work with Claude models via GPUGEEK provider. Claude supports tool/function calling natively but not OpenAI's response_format JSON mode. - Increase recursion_limit from 25 to 50 in agent.astream() to handle complex research tasks with more iterations. Co-authored-by: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: xinquiry(SII) <[email protected]> Co-authored-by: Claude <[email protected]>
* feat: add drag-and-drop agent reordering and auto-update on version mismatch (#206) * fix: resolve i18next missing key warnings in TierInfoModal Replace hardcoded Chinese strings with proper i18n translation keys in the tier selector component. This fixes console warnings about missing translation keys when using the zh locale. - Add speedLabels, reasoningLabels, and features keys to app.json - Add multiplier key to tierSelector in all locales - Add recommended key to common.json in all locales - Refactor TierInfoModal.tsx to use translation key references Co-Authored-By: Claude <[email protected]> * feat: add drag-and-drop agent reordering Integrate dnd-kit into existing AgentList and AgentListItem components to support drag-and-drop reordering in both the sidebar and spatial focused view. Backend: - Add sort_order field to Agent model - Add PATCH endpoint for bulk reordering agents - Add migration for sort_order column Frontend: - Add sortable prop to AgentList with DndContext/SortableContext - Add dragHandleProps to AgentListItem for drag behavior - Use plain div (not motion.div) when sortable to avoid animation conflicts - Use set-based comparison for state sync (only reset on add/remove) - Add reorderAgents action to agentSlice * feat: auto-refresh frontend on version mismatch with backend Add automatic update mechanism that detects when the frontend version doesn't match the backend version and refreshes the page to fetch the latest assets. This ensures users with cached frontends always get updated code without manually clearing their browser cache. - Add useAutoUpdate hook with retry logic (max 3 attempts) - Add UpdateOverlay component for update feedback - Clear service workers and caches before reload - Store retry state in localStorage to prevent infinite loops * fix: address PR review feedback for drag-and-drop agent reordering - Move AgentList state sync from render to useEffect to prevent render loops - Add isDraggingRef to prevent backend sync during active drag operations - Restore keyboard accessibility to CompactAgentListItem (role, tabIndex, aria-pressed, onKeyDown handler) - Guard localStorage writes with try/catch in useAutoUpdate to handle restricted environments --------- Co-authored-by: Claude <[email protected]> * fix: resolve version mismatch causing refresh loops in test environment (#207) * fix: move the comment to correct position * fix: fix the test environment version * fix: remove auto-update feature to prevent refresh loops The auto-update mechanism causes infinite refresh loops when frontend and backend versions mismatch in test environment. Remove the feature entirely until a more robust solution is implemented. - Delete useAutoUpdate hook and UpdateOverlay component - Remove AutoUpdateWrapper from App.tsx * Fix: Unify tool call UI (pills in agent timeline) for streaming + refresh (#208) * feat: add simple landing page and logout button * fix(web): unify tool call rendering in agent timeline after refresh; add landing page - Render tool calls as pills with a shared details modal (args/results/errors) - Attach historical tool_call tool messages into agentExecution phases instead of standalone messages - Remove legacy ToolCallCard-based rendering path * fix(web): address review feedback for tool call modal + typewriter - Move tool-call UI strings into i18n (app.chat.toolCall.*) for en/zh/ja - Memoize tool result parsing and image URL derivation in ToolCallDetails - Avoid duplicate argument headings in ToolCallDetailsModal for waiting_confirmation - Remove redundant typewriter cursor className conditional and fix unused state var * feat: message editing and deletion (#209) * feat: add message editing and deletion with truncate-regenerate flow - Add PATCH /messages/{id} endpoint for editing user messages - Add DELETE /messages/{id} endpoint for deleting any message - Add regenerate WebSocket handler for re-running agent after edit - Add edit/delete UI to ChatBubble with hover actions - Add i18n translations for en/zh/ja Includes fixes from code review: - Fix pre-deduction error handling to skip dispatch on any failure - Reset responding state before regeneration to prevent stuck UI - Add message ownership verification before edit/delete operations * fix: improve the code according to sourcery review * fix: use version+SHA for beta image tags to ensure unique deployments (#211) The previous approach used only the pyproject.toml version (e.g., 1.0.16) which caused Kubernetes to not pull new images when multiple commits used the same version tag. Now uses format: {version}-{short-sha} (e.g., 1.0.16-f13e3c0) Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * feat: conversation interrupt/abort functionality (#212) * feat: add conversation interrupt/abort functionality - Add stop button and Escape key shortcut to abort streaming generation - Implement Redis-based signaling between API server and Celery worker - Worker checks abort signal every 0.5s and gracefully stops streaming - Save partial content and perform partial billing on abort - Add visual indicator for cancelled/aborted messages - Add timeout fallback (10s) to reset UI if backend doesn't respond - Add i18n strings for stop/stopping/escToStop in en/zh/ja * fix: address abort feature edge cases from code review - Clear stale abort signals at task start to prevent race condition when user reconnects quickly after disconnect - Finalize AgentRun with 'failed' status on unhandled exceptions to ensure consistent DB state across all exit paths - Move time import to module level (was inline import) * fix: address Sourcery review feedback for abort feature - Reuse existing Redis client for abort checks instead of creating new connections on each tick (performance improvement) - Fix potential Redis connection leaks in ABORT and disconnect handlers by using try/finally pattern - Track and cleanup abort timeout in frontend to prevent stale timers from racing with subsequent abort requests * Preserve phase text when copying streamed output (#210) * Preserve phase text when copying streamed output * Extract agent phase content helper * fix: use existing PhaseExecution type and correct useMemo dependencies - Replace inline PhaseWithStreamedContent type with Pick<PhaseExecution, 'streamedContent'> for type consistency across the codebase - Fix useMemo dependency array to use agentExecution?.phases instead of agentExecution to ensure proper recalculation when phases array changes * Fix streaming agent messages and prevent deleting non-persisted messages (#213) * Fix message deletion for agent executions * Address Sourcery review: stricter UUID validation and safer id assignment - Add isValidUuid utility with canonical UUID pattern (8-4-4-4-12 format) to replace loose regex that accepted invalid strings like all-hyphens - Fix streaming_start handler to only set id when eventData.id is truthy, preventing accidental overwrites with undefined/null - Improve delete guard with contextual messages ("still streaming" vs "not saved yet") and change notification type to warning - Add comprehensive tests for isValidUuid covering valid UUIDs, client IDs, invalid formats, and edge cases Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * fix: emit message_saved event after stream abort (#215) * fix: emit message_saved event after stream abort When a user interrupts a streaming response, the message is saved to the database but the frontend never receives the message_saved event. This leaves the message with a temporary stream_ prefix ID, preventing deletion until page refresh. Now the MESSAGE_SAVED event is emitted after db.commit() in the abort handler, before STREAM_ABORTED, so the frontend updates the message ID to the real UUID and deletion works immediately. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * fix: always show latest topic when clicking an agent Unify sidebar and spatial workspace to use the same logic for selecting topics. Both now fetch from backend and always show the most recently updated topic (by updated_at) instead of remembering previously active topics. Co-Authored-By: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * feat: improve message editing UX with edit-only option and assistant editing - Add "Edit" and "Edit & Regenerate" dropdown options for user messages - Allow editing assistant messages (content-only, no regeneration) - Add copy button to user messages - Move assistant message actions to top-right for better UX - Add auto-resizing textarea for editing long messages - Update backend to support truncate_and_regenerate flag Co-Authored-By: Claude Opus 4.5 <[email protected]> * refactor: extract message content resolution into dedicated module Extract scattered content resolution logic into core/chat/messageContent.ts with two main utilities: - resolveMessageContent(): Single source of truth for content priority - getMessageDisplayMode(): Explicit rendering mode determination This refactoring: - Reduces ChatBubble.tsx complexity (60+ line IIFE → 30 line switch) - Fixes inconsistency between copy/edit and display logic - Makes content source priority explicit and documented - Adds guard for empty content to avoid rendering empty divs - Improves maintainability with testable pure functions Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude (Vendor2/Claude-4.5-Opus) <[email protected]> * fix: deep research structured output and recursion limit (#216) - Use function_calling method for structured output in clarify node. The default json_mode doesn't work with Claude models via GPUGEEK provider. Claude supports tool/function calling natively but not OpenAI's response_format JSON mode. - Increase recursion_limit from 25 to 50 in agent.astream() to handle complex research tasks with more iterations. Co-authored-by: Claude Opus 4.5 <[email protected]> * fix: attach thinking content to agent execution message When agent_start event arrives before thinking_start, it consumes the loading message. The thinking_start handler then couldn't find a loading message and created a separate thinking message, causing the thinking content to appear as a separate bubble below the agent response. Fix the thinking event handlers to also check for running agent execution messages and attach thinking content to them instead of creating separate messages. Co-Authored-By: Claude Opus 4.5 <[email protected]> * fix: align thinking_end condition with thinking_chunk Use consistent condition `m.agentExecution?.status === "running"` in both thinking_chunk and thinking_end handlers for finding agent messages. Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude <[email protected]>
变更内容
简要描述本次 PR 的主要变更内容。
相关 Issue
请关联相关 Issue(如有):#编号
检查清单
默认已勾选,如不满足,请检查。
其他说明
如有特殊说明或注意事项,请补充。
Summary by Sourcery
为代理添加支持后端持久化的拖放排序功能,并在前后端版本不匹配时,引入自动的前端更新处理机制。
New Features:
sort_order字段、重新排序的 API 端点以及客户端存储逻辑,为每个用户持久化自定义代理排序。Enhancements:
sort_order值对获取到的代理进行排序,并在创建新代理时为其分配连续的sort_order。Build:
@dnd-kit/sortable依赖,用于支持列表的拖放排序。Original summary in English
Summary by Sourcery
Add drag-and-drop reordering for agents with backend persistence and introduce automatic frontend update handling based on backend version mismatches.
New Features:
Enhancements:
Build: